#include "../lib/coresettings.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <boost/algorithm/string.hpp>


#include "../lib/coreutil.hpp"

#define BUFFSIZE 1024

using namespace pland;

Settings* Settings::_instance = NULL;

Settings::Settings()
{
    port_= CORE_SETTINGS_PORT;
    running_ = false;
}

Settings* Settings::Instance()
{
	if(_instance == NULL)
		_instance = new Settings();
	return _instance;
}

void Settings::setPort(uint port)
{
    port_ = port;
}

bool Settings::setSetting(std::string key, std::string value)
{
	boost::algorithm::trim(key);
	boost::algorithm::trim(value);
    boost::unique_lock< boost::shared_mutex > lock(mutex_);
	_settings[key]= value;
	return true;
}

std::string Settings::getSetting(std::string key)
{
	boost::algorithm::trim(key);
    boost::shared_lock< boost::shared_mutex > lock(mutex_);
	std::map<std::string, std::string>::iterator pos = _settings.lower_bound(key);
	if(pos != _settings.end() && !(_settings.key_comp()(key, pos->first)))
	{
		return pos->second;
	}
	return "";
}

bool Settings::isSet(std::string key)
{
	boost::algorithm::trim(key);
    boost::shared_lock< boost::shared_mutex > lock(mutex_);
	std::map<std::string, std::string>::iterator pos = _settings.lower_bound(key);
	if(pos != _settings.end() && !(_settings.key_comp()(key, pos->first)))
	{
		return true;
	}
	return false;
}

std::string Settings::erase(std::string key)
{
	boost::algorithm::trim(key);
    boost::unique_lock< boost::shared_mutex > lock(mutex_);
	std::string out = "";
	std::map<std::string, std::string>::iterator pos = _settings.lower_bound(key);
	if(pos != _settings.end() && !(_settings.key_comp()(key, pos->first)))
	{
		out = pos->second;
		_settings.erase(pos);
	}
	return out;
}

void Settings::clear()
{
    boost::unique_lock< boost::shared_mutex > lock(mutex_);
    _settings.clear();
}

bool Settings::start()
{
    boost::unique_lock< boost::shared_mutex > lock(mutex_);
    if(running_ == true)
	{
		return false;
	}
    running_ = true;
    listener_ = boost::thread(&Settings::start_, this);
    return running_;
}

bool Settings::stop()
{
    boost::unique_lock< boost::shared_mutex > lock(mutex_);
    if(running_ == false)
	{
		return false;
	}
    listener_.interrupt();
    listener_.join();
    running_ = false;
    return running_;
}

void Settings::start_()
{
	int sockfd = -1;
	int newsockfd = -1;
	int yes = 1;
	int n;
	socklen_t clilen;
	char buff[BUFFSIZE];
	struct sockaddr_in serv_addr, cli_addr;
	try
	{
		sockfd = socket(AF_INET, SOCK_STREAM, 0);
		if (sockfd < 0)
		{
            boost::unique_lock< boost::shared_mutex > lock(mutex_);
			utill_log(std::string(__FILE__) + " Error creating socket");
            running_ = false;
			return;
		}
		bzero((char *) &serv_addr, sizeof(serv_addr));
		serv_addr.sin_family = AF_INET;
		serv_addr.sin_addr.s_addr = INADDR_ANY;
        serv_addr.sin_port = htons(port_);
		if (bind(sockfd, (struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0)
		{
            boost::unique_lock< boost::shared_mutex > lock(mutex_);
            utill_log(std::string(__FILE__) + " Error binding to port " + utill_to_string(port_));
			close(sockfd);
			return;
		}
		if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0)
		{
            boost::unique_lock< boost::shared_mutex > lock(mutex_);
            utill_log(std::string(__FILE__) + " Error setting socket options " + utill_to_string(port_));
			close(sockfd);
			return;
		}		
		listen(sockfd,5);
		clilen = sizeof(cli_addr);
        utill_log(std::string(__FILE__) + " Listening on port " + utill_to_string(port_));
		while(1)
		{
			newsockfd = accept(sockfd,(struct sockaddr *) &cli_addr, &clilen);
			if(newsockfd < 0)
				continue;
			n = read(newsockfd, buff, BUFFSIZE - 1);
			buff[n] = '\0';

			switch(buff[0])
			{
			case 'G':
				{
					std::string key(buff+1);
					boost::algorithm::trim(key);
					if(key == "*")
					{
                        boost::shared_lock< boost::shared_mutex > lock(mutex_);
						std::map<std::string, std::string>::iterator i = _settings.begin();
						std::string value = "";
						for( ; i != _settings.end(); ++i )
						{
							value += i->first +":"+i->second +"\n";
						}
						write(newsockfd, value.c_str(), value.size());
					}
					else
					{
						std::string value = key + ":" + this->getSetting(key) + "\n";
						write(newsockfd, value.c_str(), value.size());
					}
					break;
				}
			case 'S':
				{
					size_t pos;
					std::string values(buff+1);
					boost::algorithm::trim(values);
					pos = values.find_first_of(':',0);
					if(pos == std::string::npos)
					{
						break;
					}
					std::string key = values.substr(0, pos);
					std::string value = values.substr(pos+1, values.length());
					boost::algorithm::trim(key);
					boost::algorithm::trim(value);
					setSetting(key,value);
					break;
				}
			default:
				break;
			}
			close(newsockfd);
		}
	}
	catch(boost::thread_interrupted e)
	{
        running_ = false;
		if(sockfd > 0)
			close(sockfd);
		return;
	}
}
